package top.shaozuo.simple.configuration.microprofile.config.converts;

import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.ServiceLoader;

import org.eclipse.microprofile.config.spi.Converter;

public class Converters implements Iterable<Converter> {

	private static final int DEFAULT_PRIORITY = 100;

	private boolean addedDiscoveredConverters = false;

	private ClassLoader classLoader;
	private Map<Class<?>, PriorityQueue<PrioritizedConverter>> typedConverters = new HashMap<>();

	public Converters(ClassLoader classLoader) {
		super();
		this.classLoader = classLoader;
		registerDefaultConverter();
	}

	public ClassLoader getClassLoader() {
		return classLoader;
	}

	public void setClassLoader(ClassLoader classLoader) {
		this.classLoader = classLoader;
	}

	private void registerDefaultConverter() {
		addConverter(Boolean.class, BooleanConverter.INSTANCE);
		addConverter(boolean.class, BooleanConverter.INSTANCE);
		addConverter(Byte.class, ByteConverter.INSTANCE);
		addConverter(byte.class, ByteConverter.INSTANCE);
		addConverter(Short.class, ShortConverter.INSTANCE);
		addConverter(short.class, ShortConverter.INSTANCE);
		addConverter(Integer.class, IntegerConverter.INSTANCE);
		addConverter(int.class, IntegerConverter.INSTANCE);
		addConverter(Long.class, LongConverter.INSTANCE);
		addConverter(long.class, LongConverter.INSTANCE);
		addConverter(Float.class, FloatConverter.INSTANCE);
		addConverter(float.class, FloatConverter.INSTANCE);
		addConverter(Double.class, DoubleConverter.INSTANCE);
		addConverter(double.class, DoubleConverter.INSTANCE);
		addConverter(String.class, StringConverter.INSTANCE);

	}

	public void addDiscoveredConverters() {
		if (addedDiscoveredConverters) {
			return;
		}
		addConverters(ServiceLoader.load(Converter.class, classLoader));
		addedDiscoveredConverters = true;
	}

	private void addConverters(Iterable<Converter> converters) {
		converters.forEach(this::addConverter);
	}

	private void addConverter(Converter converter) {
		addConverter(converter, DEFAULT_PRIORITY);

	}

	public void addConverter(Class<?> convertedType, Converter converter) {
		addConverter(convertedType, DEFAULT_PRIORITY, converter);
	}

	public void addConverter(Converter converter, int priority) {
		Class<?> convertedType = resolveConvertedType(converter);
		addConverter(convertedType, priority, converter);
	}

	public void addConverter(Class<?> convertedType, int priority, Converter converter) {
		PriorityQueue priorityQueue = typedConverters.computeIfAbsent(convertedType, t -> new PriorityQueue<>());
		priorityQueue.offer(new PrioritizedConverter(converter, priority));
	}

	protected Class<?> resolveConvertedType(Converter<?> converter) {
		assertConverter(converter);
		Class<?> convertedType = null;
		Class<?> converterClass = converter.getClass();
		while (converterClass != null) {
			convertedType = resolveConvertedType(converterClass);
			if (convertedType != null) {
				break;
			}

			Type superType = converterClass.getGenericSuperclass();
			if (superType instanceof ParameterizedType) {
				convertedType = resolveConvertedType(superType);
			}

			if (convertedType != null) {
				break;
			}
			// recursively
			converterClass = converterClass.getSuperclass();

		}

		return convertedType;
	}

	private void assertConverter(Converter<?> converter) {
		Class<?> converterClass = converter.getClass();
		if (converterClass.isInterface()) {
			throw new IllegalArgumentException("The implementation class of Converter must not be an interface!");
		}
		if (Modifier.isAbstract(converterClass.getModifiers())) {
			throw new IllegalArgumentException("The implementation class of Converter must not be abstract!");
		}
	}

	private Class<?> resolveConvertedType(Class<?> converterClass) {
		Class<?> convertedType = null;

		for (Type superInterface : converterClass.getGenericInterfaces()) {
			convertedType = resolveConvertedType(superInterface);
			if (convertedType != null) {
				break;
			}
		}

		return convertedType;
	}

	private Class<?> resolveConvertedType(Type type) {
		Class<?> convertedType = null;
		if (type instanceof ParameterizedType) {
			ParameterizedType pType = (ParameterizedType) type;
			if (pType.getRawType() instanceof Class) {
				Class<?> rawType = (Class) pType.getRawType();
				if (Converter.class.isAssignableFrom(rawType)) {
					Type[] arguments = pType.getActualTypeArguments();
					if (arguments.length == 1 && arguments[0] instanceof Class) {
						convertedType = (Class) arguments[0];
					}
				}
			}
		}
		return convertedType;
	}

	public void addConverters(Converter<?>[] converters) {
		addConverters(Arrays.asList(converters));
	}

	public List<Converter> getConverters(Class<?> convertedType) {
		PriorityQueue<PrioritizedConverter> prioritizedConverters = typedConverters.get(convertedType);
		if (prioritizedConverters == null || prioritizedConverters.isEmpty()) {
			return Collections.emptyList();
		}
		List<Converter> converters = new LinkedList<>();
		for (PrioritizedConverter prioritizedConverter : prioritizedConverters) {
			converters.add(prioritizedConverter.getConverter());
		}
		return converters;
	}

	@Override
	public Iterator<Converter> iterator() {
		List<Converter> allConverters = new LinkedList<>();
		for (PriorityQueue<PrioritizedConverter> converters : typedConverters.values()) {
			for (PrioritizedConverter converter : converters) {
				allConverters.add(converter.getConverter());
			}
		}
		return allConverters.iterator();
	}
}
